/*
 * Copyright (C) Cátedra SAES-UMU 2011 <pedromateo@um.es>
 *
 * Android HCI Extractor is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Android HCI Extractor is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.mmi.android.instrumentation;

import java.util.LinkedList;

import org.mmi.android.instrumentation.filters.AppTimingFilter;
import org.mmi.android.instrumentation.filters.KeyInputFilter;
import org.mmi.android.instrumentation.filters.NavigationInputFilter;
import org.mmi.android.instrumentation.filters.NewChildFilter;
import org.mmi.android.instrumentation.filters.SpeechInputFilter;
import org.mmi.android.instrumentation.filters.ViewChangesFilter;
import org.mmi.android.instrumentation.metafilters.CompositeFilter;
import org.mmi.android.instrumentation.sentinel.SentinelView;
import org.mmi.facades.BaseMmiFacade;

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;

/**
 * Global context for the instrumentation process.
 * 
 * @author pedro mateo (pedromateo@um.es)
 *
 */
public class InstrumentationContext {

	private static final String TAG = "InstrumentationContext";

	///
	/// singleton functionality
	///

	/// Singleton instance
	private static InstrumentationContext instance = null;

	/// facade
	private static BaseMmiFacade mmifacade;

	/**
	 * Protected constructor
	 */
	protected InstrumentationContext() {
		//check facade

		if (mmifacade == null)
			throw new NullPointerException("BaseMmiFacade object is null. Please, call the reset(BaseMmiFacade facade) method with a non null facade object before this line of code.");

		//create filters
		appTimingFilter = new AppTimingFilter(mmifacade);
		navigationInputFilter = new NavigationInputFilter(mmifacade);
		keyInputFilter = new KeyInputFilter(mmifacade);
		viewChangesFilter = new ViewChangesFilter(mmifacade);
		speechInputFilter = new SpeechInputFilter(mmifacade);

		//create a composite filter
		allFilters = new CompositeFilter(mmifacade);
		allFilters.addSubFilter(appTimingFilter);
		allFilters.addSubFilter(navigationInputFilter);
		allFilters.addSubFilter(keyInputFilter);
		allFilters.addSubFilter(viewChangesFilter);
		allFilters.addSubFilter(speechInputFilter);

		//create new child filter
		newChildFilter = new NewChildFilter(mmifacade,allFilters);
		//also add this filter to the composite one
		allFilters.addSubFilter(newChildFilter);

		//add all filters to an internal list
		filters_ = new LinkedList<InteractionListener>();
		filters_.add(appTimingFilter);
		filters_.add(navigationInputFilter);
		filters_.add(keyInputFilter);
		filters_.add(viewChangesFilter);
		filters_.add(newChildFilter);
		filters_.add(speechInputFilter);

		//initialize application context
		activityManager = null;
		appContext = null;
		mainActivity = null;

		//test log outputs
		Log.v(TAG, "log VERBOSE is enabled");
		Log.d(TAG, "log DEBUG is enabled");
		Log.i(TAG, "log INFO is enabled");
		Log.w(TAG, "log WARNING is enabled");
		Log.e(TAG, "log ERROR is enabled");

		Log.i(TAG,"Instrumentation context ready.");
	}

	/**
	 * Singleton get method
	 * @return The global singleton instance
	 */
	public static InstrumentationContext get() {
		if(instance == null) {
			instance = new InstrumentationContext();
		}
		else{
			//Log.d(TAG,"Instrumentation context already exists.");
		}
		return instance;
	}

	/**
	 * Bug overcoming during testing process:
	 * library is not removed when application is restarted.
	 * This method creates a new context.
	 */
	public static void reset(BaseMmiFacade facade) {

		if (facade == null)
			throw new NullPointerException("BaseMmiFacade object is null");

		mmifacade = facade;

		instance = new InstrumentationContext();
		Log.d(TAG,"Instrumentation context reset.");
	}

	/**
	 * Bug overcoming during testing process:
	 * library is not removed when application is restarted.
	 * This method creates a new context.
	 * @param a the Main Activity of the Android app
	 */
	public static void reset(Activity mainActivity) {
		instance = new InstrumentationContext();
		instance.setMainActivity(mainActivity);
		Log.d(TAG,"Instrumentation context reset.");
	}

	///
	/// private control variables
	///

	private LinkedList<InteractionListener> filters_;

	///
	/// public variables
	///

	//android application context
	public ActivityManager activityManager;
	public Context appContext;
	public Activity mainActivity;

	//meta-filters
	public NewChildFilter newChildFilter;
	//filters
	public CompositeFilter allFilters;
	public AppTimingFilter appTimingFilter;
	public NavigationInputFilter navigationInputFilter;
	public KeyInputFilter keyInputFilter;
	public ViewChangesFilter viewChangesFilter;
	public SpeechInputFilter speechInputFilter;


	///
	/// public methods
	///

	/**
	 * Method to notify "start" to interaction listeners
	 */
	public void startInteraction(){
		for (InteractionListener ic : filters_){
			ic.onInteractionStart();
		}
	}

	/**
	 * Method to notify "end" to interaction listeners
	 */
	public void endInteraction(){
		for (InteractionListener ic : filters_){
			ic.onInteractionEnd();
		}
	}

	/**
	 * Registers a new activity object into the instrumentation process
	 * using the default set of filters
	 * @param a the activity
	 * @throws InstrumentationException 
	 */
	public void registerActivity(Activity a){
		if (a == null){
			Log.w(TAG,"registerActivity :: Activity is null");
			return;
		}

		//add activity to the list and
		//install default filter on Views
		registerActivity(a,allFilters);
	}

	/**
	 * Registers the main activity object into the instrumentation process
	 * using the default set of filters
	 * @param a the activity
	 * @throws InstrumentationException 
	 */
	public void registerMainActivity(Activity a) throws InstrumentationException{
		//set the main activity
		if (a == null)
			throw new InstrumentationException("In registerMainActivity :: Main activity is null.");
		if (mainActivity != null)
			throw new InstrumentationException("In registerMainActivity :: Main activity already exists.");
		mainActivity = a;
		//add activity to the list and
		//install default filter on Views
		registerActivity(a,allFilters);
	}

	/**
	 * Registers a new activity into the instrumentation process
	 * using a provided set of filters
	 * @param a the activity
	 * @param f the filter/s
	 * @throws InstrumentationException 
	 */
	public void registerActivity(Activity a, BaseFilter f){
		if (a == null){
			Log.w(TAG,"registerActivity :: Activity is null");
			return;
		}

		//add a sentinel view
		LayoutParams lp = new LayoutParams(0, 0);
		a.addContentView(new SentinelView(a.getApplicationContext()),lp);


		//install filter on Views
		//f.installOnTop(a.getWindow().getDecorView());
		f.installOnView(a.getWindow().getDecorView());

		//set application context
		if (activityManager == null)
			activityManager = 
			(ActivityManager)a.getSystemService(Context.ACTIVITY_SERVICE);
		if (appContext == null)
			appContext = a.getApplicationContext();
	}

	/**
	 * Returns the main Activity of the Android application
	 * @return
	 */
	public Activity getMainActivity(){
		return mainActivity;
	}

	/**
	 * Sets the main Activity of the Android application
	 * @param a the main activity
	 */
	public void setMainActivity(Activity a){
		mainActivity = a;
	}

	/**
	 * Saves the interaction on a XML file
	 * @param file the file path
	 */
	/*public void saveInteraction(String file){
		mmifacade.saveDialogue(file);
	}*/

	/**
	 * Returns the current dialogue object.
	 * @return the dialogue
	 */
	/*public Dialogue getCurrentInteraction(){
		return mmifacade.getCurrentDialogue();
	}*/

	/**
	 * Returns the current document-root object to manage
	 * all the XML properties.
	 * @return the document-root
	 */
	/*public DocumentRoot getCurrentDocumentRoot(){
		return mmifacade.getCurrentDocumentRoot();
	}*/
}
